package edu.pw.treegrid.server.web;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.apache.log4j.Logger;
import org.apache.poi.ss.usermodel.Workbook;

import xalan.smartgwt.criteria.Criteria;
import xalan.smartgwt.criteria.CriteriaParser;
import xalan.smartgwt.record.Response;
import edu.pw.treegrid.server.filter.AndFilter;
import edu.pw.treegrid.server.filter.ComplexFilter;
import edu.pw.treegrid.server.filter.Filter;
import edu.pw.treegrid.server.message.Hierarchy;
import edu.pw.treegrid.server.message.Path;
import edu.pw.treegrid.server.message.Request;
import edu.pw.treegrid.server.message.ResponseFetchChilds;
import edu.pw.treegrid.server.model.SecondDimensionObject;
import edu.pw.treegrid.server.query.QueryGenerator;
import edu.pw.treegrid.server.reportmodel.Report;
import edu.pw.treegrid.server.reportmodel.ReportColumn;
import edu.pw.treegrid.server.reportmodel.ReportHierarchy;
import edu.pw.treegrid.server.service.TableRow2DomainObjectConverter;
import edu.pw.treegrid.server.service.TreeGridService;
import edu.pw.treegrid.server.service.TreeGridServiceImpl;
import edu.pw.treegrid.server.service.XMLMarshaller;
import edu.pw.treegrid.server.view.CSVView;
import edu.pw.treegrid.server.view.ExcelView;
import edu.pw.treegrid.server.web.converters.DomainObjectConverter;
import edu.pw.treegrid.server.web.converters.PairConverter;
import edu.pw.treegrid.server.web.converters.ReportColumnConverter;
import edu.pw.treegrid.server.web.converters.ReportConverter;
import edu.pw.treegrid.server.web.converters.SecondDimensionObjectConverter;
import edu.pw.treegrid.shared.Configuration;
import edu.pw.treegrid.shared.ReportColumnDescription;

/**
 * Servlet implementation class ReportServlet
 */
abstract public class ReportServlet extends HttpServlet {
	private static final String REPOSITORY_DIRECTORY = "/WEB-INF/repository/";
	
	private static final long serialVersionUID = 1L;

	protected Logger logger = Logger.getLogger("xalan.reports");
	
	abstract protected DataSource getDataSource();

	private XMLMarshaller marshaller;
	
	private TreeGridService service;
	
	private DataSource dataSource;
	
	private ReportColumnConverter reportColumnConverter;
	
	public XMLMarshaller getMarshaller() {
		return marshaller;
	}

	@Override
	public void init() throws ServletException {
		super.init();
		
		_initBeans();
		String repository = this.getServletContext().getRealPath(REPOSITORY_DIRECTORY);
		marshaller.setRepositoryDirectory(repository);
		marshaller.deserializeClasses();

	}
	
	protected void _initBeans() {
		this.marshaller = new XMLMarshaller();
		TreeGridServiceImpl service = new TreeGridServiceImpl();
		service.setConverter(new TableRow2DomainObjectConverter());
		this.dataSource = getDataSource();
		this.reportColumnConverter = new ReportColumnConverter();
		this.reportColumnConverter.setService(service);
		service.setDataSource(this.dataSource);
		service.setQueryGenerator(new QueryGenerator());
		this.service = service;
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doService(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doService(request, response);
	}

	protected void doService(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		
		logRequest(request);
		
		String uri = request.getRequestURI();
		String reportId = request.getParameter(Configuration.REPORT_ID_PARAM);
		
		if(uri.contains(Configuration.META_MODEL_URL)) {
			fetchMetaModel(response, reportId);
		}
		else if(uri.contains(Configuration.DATA_URL)) {
			fetchData(request, response, reportId);
		}
		else if(uri.contains(Configuration.EXCEL_URL)) {
			onExportExcel(reportId, request, response);
		}
		else if(uri.contains(Configuration.CSV_URL)) {
			onExportCSV(reportId, request, response);
		}
		else if(uri.contains(Configuration.HIERARCHIES_URL)) {
			fetchHierarchies(reportId, request, response);
		}
		else if(uri.contains(Configuration.SECOND_DIMENSION_URL)) {
			fetchSecondDimension(reportId, request, response);
		}
		else if(uri.contains(Configuration.REPORT_URL)) {
			fetchReportData(reportId, request, response);
		}
		else if(uri.contains("xls1")) {
			xls1(reportId, request, response);
		}
		else if(uri.contains("xls2")) {
			xls2(reportId, request, response);
		}
		else if(uri.contains("xls3")) {
			xls3(reportId, request, response);
		}
		else {
			throw new ServletException("Nie znana akcja!");
		}
	}

	private void fetchReportData(String reportId, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		Report report = marshaller.deserializeReport(reportId);
		List<Report> r = new LinkedList<Report>();
		r.add(report);
		Response.sendBeans(response, r, new ReportConverter());
	}

	private void fetchHierarchies(String reportId, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		Report report = marshaller.deserializeReport(reportId);
		List<Pair> result = new LinkedList<Pair>();
		for(ReportHierarchy rh: report.getStandardHierarchies()) {
			for(ReportColumn col: rh.getColumns()) {
				result.add(new Pair(rh.getHierarchyName(), col.getName()));
			}
		}
		Response.sendBeans(response, result, new PairConverter());
	}
	
	private void fetchSecondDimension(String reportId, 
			HttpServletRequest request, 
			HttpServletResponse response) throws IOException {
		logger.info("fetchSecondDimension() poczatek");
		// pobranie wszystkich wierszy danych drugiego wymiaru
		Report report = marshaller.deserializeReport(reportId);
		List<SecondDimensionObject> elements = service.fetchSecondDimension(report);
		
		Response.sendBeans(response, elements, new SecondDimensionObjectConverter());
		logger.info("fetchSecondDimension() koniec");
	}

	public void fetchData(HttpServletRequest request,
			HttpServletResponse response, String reportId) throws IOException {
		logger.info("fetchData() poczatek");
		String parentPath = request.getParameter(Configuration.PARENT_PATH_PARAM);
		String hierarchy = request.getParameter(Configuration.HIERARCHY_PARAM);
		
		Request requestFetchChilds = new Request();
		Report report = marshaller.deserializeReport(reportId);
		requestFetchChilds.setPath(new Path(parentPath));
		requestFetchChilds.setReport(report);
		requestFetchChilds.setHierarchy(buildHierarchyFromString(report, hierarchy));
		if(requestFetchChilds.getHierarchy().getGroupColumns().isEmpty()) {
			// Jak brak hierarchii to nic nie zwracaj
			return;
		}
		requestFetchChilds.setFilter(getFilter(request, report));

		ResponseFetchChilds responseFetchChilds = new ResponseFetchChilds();
		service.fetchChilds(requestFetchChilds, responseFetchChilds);
		
		Response.sendBeans(response, responseFetchChilds.getDomainObjects(), new DomainObjectConverter());
		logger.info("fetchData() koniec");
	}
	
	/**
	 * Pobiera filtr dotyczacy uprawnien. Klasa pochodna moze przeciazyc te metode i podac odpowiedni filtr.
	 * 
	 * @param report
	 * @return
	 */
	protected ComplexFilter getPrivilegesFilter(HttpServletRequest request, Report report) {
		return null;
	}

	private Filter getFilter(HttpServletRequest request, Report report) {
		CriteriaParser parser = new CriteriaParser();
		String[] criteriaParams = request.getParameterValues("criteria");
		String operatorParam = request.getParameter("operator");
		Filter result = null;
		Filter smartGWTFilter = null;
		if(criteriaParams != null && operatorParam != null) {
			String criteriaString = getCriteriaString(criteriaParams, operatorParam);
			Criteria criteria = parser.parse(criteriaString);
			smartGWTFilter = new SmartGWTFilter(criteria);
		}
		ComplexFilter privilegesFilter = getPrivilegesFilter(request, report);
		
		if(smartGWTFilter == null) {
			result = privilegesFilter;
		}
		else if(privilegesFilter == null || privilegesFilter.getFilters().isEmpty()) {
			result = smartGWTFilter;
		}
		else {
			AndFilter andFilter = new AndFilter();
			andFilter.addFilter(privilegesFilter);
			andFilter.addFilter(smartGWTFilter);
			result = andFilter;
		}
		
		return result;
	}

	private String getCriteriaString(String criteriaParams[], String op) {
		StringBuilder sb = new StringBuilder();
		sb.append("{\"_constructor\": \"AdvancedCriteria\", \"operator\":\"");
		sb.append(op);
		sb.append("\",\"criteria\": [");
		boolean first = true;
		for(String s: criteriaParams) {
			if(first) {
				first = false;
			}
			else {
				sb.append(",");
			}
			sb.append(s);
		}
		sb.append("]}");
		logger.info(sb.toString());
		return sb.toString();
	}

	public void fetchMetaModel(HttpServletResponse response, String reportId)
			throws IOException {
		Report report = marshaller.deserializeReport(reportId);
		List<ReportColumn> columns = new LinkedList<ReportColumn>(report.getColumns());
		columns.add(new ReportSpecialColumn(ReportColumnDescription.PATH_COLUMN));
		columns.add(new ReportSpecialColumn(ReportColumnDescription.PARENT_PATH_COLUMN));
		columns.remove(report.getNameColumn());
		ReportSpecialColumn rsc = new ReportSpecialColumn(ReportColumnDescription.NAME_COLUMN);
		rsc.setWidth("20%");
		columns.add(rsc);
		Response.sendBeans(response, columns, this.reportColumnConverter);
	}


	private void onExportExcel(String reportId, HttpServletRequest request, HttpServletResponse response) {
		if(!isAllowedToExportToXLS(request)) {
			throw new RuntimeException("Brak uprawnień do exportu do pliku xls!");
		}
		Report report = marshaller.deserializeReport(reportId);
		ExcelView excelView = new ExcelView(service);
		
		Workbook workbook = excelView.buildExcelDocument(report, getFilter(request, report));
		
		response.setHeader("Content-Type", "application/vnd.ms-excel");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + getXlsFilename(reportId) + "\"");
		
		try {
			ServletOutputStream out = response.getOutputStream();
			workbook.write(out);
			out.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void onExportCSV(String reportId, HttpServletRequest request, HttpServletResponse response) {
		if(!isAllowedToExportToXLS(request)) {
			throw new RuntimeException("Brak uprawnień do exportu do pliku CSV!");
		}
		Report report = marshaller.deserializeReport(reportId);
		CSVView csvView = new CSVView(service);
		
		response.setHeader("Content-Type", "text/csv; charset=windows-1250");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + getCSVFilename(reportId) + "\"");
		
		try {
			ServletOutputStream out = response.getOutputStream();
			csvView.writeDocument(report, getFilter(request, report), out);
			out.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Metoda sprawdzajaca czy uzytkownik ma uprawnienia do exportu danych do xls.
	 * 
	 * Metoda moze byc przeciazona przez klase pochodna.
	 */
	protected boolean isAllowedToExportToXLS(HttpServletRequest request) {
		return true;
	}

	private void logRequest(HttpServletRequest request) {
		logger.info("RequestURL: " + request.getRequestURL());
		logger.info("RequestURI: " + request.getRequestURI());
		
		@SuppressWarnings("unchecked")
		Map<String, String[]> params = request.getParameterMap();
		for(String k : params.keySet()) {
			String[] p = params.get(k);
			StringBuilder sb = new StringBuilder();
			for(String s : p) {
				sb.append(s + ",");
			}
			logger.info(k + " => [" + sb.toString() + "]");
		}
	}
	
	private Hierarchy buildHierarchyFromString(Report report, String hierarchy) {
		Hierarchy h = new Hierarchy();
		
		if(hierarchy != null) {
			String trimHierarchy = hierarchy.trim();
			if(!trimHierarchy.equals("")) {
				for(String s: hierarchy.split(Path.PATH_SEPARATOR)) {
					h.addGroupColumn(report.getColumnByName(s));
				}
			}
		}
		
		return h;
	}

	
	private void xls1(String reportId, HttpServletRequest request, HttpServletResponse response) {		
		Report report = marshaller.deserializeReport(reportId);
		ExcelView excelView = new ExcelView(service);
		
		Workbook workbook = excelView.buildExcelDocument(report, getFilter(request, report));
		
		response.setHeader("Content-Type", "application/vnd.ms-excel");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + getXlsFilename(reportId) + "\"");
		
		try {
			ServletOutputStream out = response.getOutputStream();
			workbook.write(out);
			out.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void xls2(String reportId, HttpServletRequest request, HttpServletResponse response) {
		Report report = marshaller.deserializeReport(reportId);
		ExcelView excelView = new ExcelView(service);
		
		Workbook workbook = excelView.buildExcelDocument(report, getFilter(request, report));
		
		response.setHeader("Content-Type", "application/vnd.ms-excel");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + getXlsFilename(reportId) + "\"");
		
		try {
			ServletOutputStream out = response.getOutputStream();
			workbook.write(out);
			out.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void xls3(String reportId, HttpServletRequest request, HttpServletResponse response) {
		Report report = marshaller.deserializeReport(reportId);
		ExcelView excelView = new ExcelView(service);
		
		Workbook workbook = excelView.buildExcelDocument(report, getFilter(request, report));
		
		response.setHeader("Content-Type", "application/octet-stream");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + getXlsFilename(reportId) + "\"");
		
		try {
			ServletOutputStream out = response.getOutputStream();
			workbook.write(out);
			out.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	//To nie dziala na IE8:
	private void xls4(String reportId, HttpServletRequest request, HttpServletResponse response) {
		response.setHeader("Content-Description", "File Transfer");
		
		Report report = marshaller.deserializeReport(reportId);
		ExcelView excelView = new ExcelView(service);
		
		Workbook workbook = excelView.buildExcelDocument(report, getFilter(request, report));
		
		response.setHeader("Content-Disposition", "inline; filename=\"" + getXlsFilename(reportId) + "\"");
		
		try {
			ServletOutputStream out = response.getOutputStream();
			workbook.write(out);
			out.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	private String getXlsFilename(String reportId){
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd__HH_mm_ss");
		return reportId + "_" + sdf.format(new Date()) + ".xls";
	}
	
	private String getCSVFilename(String reportId){
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd__HH_mm_ss");
		return reportId + "_" + sdf.format(new Date()) + ".csv";
	}
}
